package plus.decl;

import java.util.List;

import plus.FEnv;
import plus.Gripe;
import plus.M_User;
import plus.Module;
import plus.TEnv;
import plus.Uni;
import plus.VScope;
import plus.stmt.Statement;

/**
 * This is what the Parser builds when it comes upon a module definition file
 * (as opposed to a program file); therefore this is the bit that ultimately
 * has to compile the module and export the symbols and all that. This is again
 * different from the run-time notion of the module, and different still from
 * the details of what the "module manager" tracks, which must potentially
 * map in native modules and library paths and etc.
 *  
 * @author me
 *
 */
public final class ModuleDef extends Unit {
	private final DeclBucket iface;
	private final DeclBucket impl;
	private final Statement begin;
	public ModuleDef(String name, List<D_Import> imports, DeclBucket iface, DeclBucket decls, Statement begin) {
		super(name, imports);
		assert begin != null;
		this.iface = iface;
		this.impl = decls;
		this.begin = begin;
	}
	@Override public void compile() {
		Gripe.context = this;
		Gripe.gripe("Can't (currently) compile/execute a module directly.");
	}
	@Override
	public Module asModule() { return new M_User(this); }
	/*
	Parts include:
		Module's internal name (which appears as the name of a scope...)
		List of dependencies
		Interface
		Implementation
		Start-up routine(s) (which are responsible for installing atexit(...) actions.)
	*/
	public void install() {
		// This is actually where the module is compiled.
		// Most of this may look quite a bit like code from Program.java.
		// Prepare type inference and binding environment.
		Uni.prepare(imports);
		
		// Small chunk: Make the type declarations appear in a TEnv where
		// clients can then get at them.
		TEnv.enterSub();
		for (D_Record d:iface.records()) { d.declare(); d.export(); }
		for (D_Record d:impl.records()) { d.declare(); }
		for (D_Record d:iface.records()) d.define();
		for (D_Record d:impl.records()) d.define();
		
		VScope.enterBlock();
		
		for (D_Var vd:iface.varDecls()) { vd.introduce(); vd.export(); }
		for (D_Var vd:impl.varDecls()) vd.introduce();
		
		FEnv fe = new FEnv();
		Uni.pushFE(fe);
		// The approach here is to install the fn def in both the export environment and also
		// in the local environment. It might be sufficient to place the functions in only
		// one or the other environment, with the stipulation that the "implementation"
		// environment gets stacked inside the "interface" environment during threading.
		for (D_Function fd:iface.fnDefs()) { fe.enter(fd.sym); M_User.current.fe.enter(fd.sym); }
		for (D_Function fd:impl.fnDefs()) fe.enter(fd.sym);
		for (D_Function fd:iface.fnDefs()) fd.threadScope(1);
		for (D_Function fd:impl.fnDefs()) fd.threadScope(1);
		
		// Go through the local code, hooking things up as needed.
		//current = this; // but first make sure return statements can find what they need.
		begin.threadScope();
		
		Uni.popFE();
		
		VScope.exit();
		TEnv.exit();
		if (Uni.converge()) {
			// Note well: There's no point scheduling a compile if the
			// function is not actually called ever...
//			for (D_Function fd:iface.fnDefs()) fd.sub.scheduleCompile();
//			for (D_Function fd:impl.fnDefs()) fd.sub.scheduleCompile();
			// The "begin" statement gets compiled LATER...
		} else {
			System.err.println("While Type-Checking "+__FILE__);
			System.err.println("Failed to Converge:");
			Uni.reportConvergenceFailure();
		}
		
	}
	public void compileStartup() {
		// I'm here relying on the "current dynamic extent" to be already
		// initialized as the global extent.
		begin.compile();
	}
}
